package com.fm.parser.json;

import com.fm.base.annotation.UnThreadSafe;
import org.apache.commons.text.StringEscapeUtils;

import javax.json.JsonException;
import javax.json.stream.JsonLocation;
import javax.json.stream.JsonParser;
import javax.json.stream.JsonParsingException;
import java.io.IOException;
import java.io.Reader;
import java.math.BigDecimal;
import java.util.EmptyStackException;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;

@UnThreadSafe
public class BasicJsonParser implements JsonParser {
    private Reader reader;
    private Event currentEvent;
    private Stack<Event> preEvent = new Stack<>();
    private Queue<Character> cache = new LinkedList<>();
    private StringBuffer value = new StringBuffer();
    private boolean isReadValue = false;

    public BasicJsonParser(Reader reader) {
        this.reader = reader;
    }

    private Character read() {
        if (!cache.isEmpty()) {
            return cache.poll();
        }
        try {
            char[] c = new char[1];
            if (reader.read(c) == 0) {
                return null;
            } else {
                if (c[0] == 0) {
                    return null;
                } else {
                    return c[0];
                }
            }
        } catch (IOException ioe) {
            throw new JsonException("json读取异常", ioe);
        }
    }

    private void readWhitespace() {
        Character c;
        while ((c = read()) != null) {
            if (c != 0x20 && c != 0x09 && c != 0x0a && c != 0x0d) {
                break;
            }
        }
        if (c != null) {
            cache.add(c);
        }
    }

    private void readNull() {
        Character c;
        c = read();
        if (c == null || c != 'u') {
            throw new JsonException("need null");
        }
        c = read();
        if (c == null || c != 'l') {
            throw new JsonException("need null");
        }
        c = read();
        if (c == null || c != 'l') {
            throw new JsonException("need null");
        }
    }

    private void readTrue() {
        Character c;
        c = read();
        if (c == null || c != 'r') {
            throw new JsonException("need true");
        }
        c = read();
        if (c == null || c != 'u') {
            throw new JsonException("need true");
        }
        c = read();
        if (c == null || c != 'e') {
            throw new JsonException("need true");
        }
    }

    private void readFalse() {
        Character c;
        c = read();
        if (c == null || c != 'a') {
            throw new JsonException("need false");
        }
        c = read();
        if (c == null || c != 'l') {
            throw new JsonException("need false");
        }
        c = read();
        if (c == null || c != 's') {
            throw new JsonException("need false");
        }
        c = read();
        if (c == null || c != 'e') {
            throw new JsonException("need false");
        }
    }

    private void readString(char c, boolean careObjectEnd, boolean careArrayEnd) {
        Character temp;
        boolean escape = false;
        if (c == '"') {
            while ((temp = read()) != null) {
                switch (temp) {
                    case '"':
                        if (escape) {
                            value.append(temp);
                            escape = false;
                        } else {
                            return;
                        }
                        break;
                    case '\\':
                        if (escape) {
                            value.append(temp);
                            escape = false;
                        } else {
                            escape = true;
                        }
                        break;
                    default:
                        if (escape) {
                            if (!temp.equals('"')) {
                                value.append('\\');
                            }
                            escape = false;
                        }
                        value.append(temp);
                }
            }
            throw new JsonParsingException("read string to the end", null);
        } else {
            value.append(c);
            while ((temp = read()) != null) {
                switch (temp) {
                    case ':':
                    case ',':
                        cache.add(temp);
                        int count = 0;
                        for(int i = value.length() - 1; i >= 0; --i) {
                            if (value.charAt(i) == ' ') {
                                count++;
                            } else {
                                break;
                            }
                        }
                        value.setLength(value.length() - count);
                        return;
                    case '}':
                        if (careObjectEnd) {
                            cache.add(temp);
                            return;
                        } else {
                            value.append(temp);
                        }
                        break;
                    case ']':
                        if (careArrayEnd) {
                            cache.add(temp);
                            return;
                        } else {
                            value.append(temp);
                        }
                        break;
                    default:
                        value.append(temp);
                }
            }
            throw new JsonParsingException("read string to the end", null);
        }
    }

    private void readNumber(char c, boolean careObjectEnd, boolean careArrayEnd) {
        Character temp;
        value.append(c);
        while ((temp = read()) != null) {
            switch (temp) {
                case ':':
                case ',':
                case ' ':
                    cache.add(temp);
                    return;
                case '}':
                    if (careObjectEnd) {
                        cache.add(temp);
                        return;
                    } else {
                        value.append(temp);
                    }
                    break;
                case ']':
                    if (careArrayEnd) {
                        cache.add(temp);
                        return;
                    } else {
                        value.append(temp);
                    }
                    break;
                case '0':
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':
                case '.':
                case '-':
                case '+':
                case 'e':
                case 'E':
                    value.append(temp);
                    break;
                default:
                    return;
            }
        }
        throw new JsonParsingException("read string to the end", null);
    }

    @Override
    public boolean hasNext() {
        readWhitespace();
        Character c = read();
        if (c != null) {
            switch (c) {
                case '{':
                    currentEvent = Event.START_OBJECT;
                    preEvent.push(currentEvent);
                    isReadValue = false;
                    return true;
                case '}':
                    currentEvent = Event.END_OBJECT;
                    try {
                        Event e = preEvent.pop();
                        if (e != Event.START_OBJECT) {
                            throw new JsonParsingException("meet } not meet { before", null);//TODO
                        }
                    } catch (EmptyStackException e) {
                        throw new JsonParsingException("meet } not meet { before", null);//TODO
                    }
                    return true;
                case '[':
                    currentEvent = Event.START_ARRAY;
                    preEvent.push(currentEvent);
                    isReadValue = true;
                    return true;
                case ']':
                    currentEvent = Event.END_ARRAY;
                    try {
                        Event e = preEvent.pop();
                        if (e != Event.START_ARRAY) {
                            throw new JsonParsingException("meet ] not meet [ before", null);//TODO
                        }
                    } catch (EmptyStackException e) {
                        throw new JsonParsingException("meet ] not meet [ before", null);//TODO
                    }
                    return true;
                case ':':
                    isReadValue = true;
                    return hasNext();
                case ',':
                    isReadValue = preEvent.peek() != Event.START_OBJECT;
                    return hasNext();
                case 't':
                    readTrue();
                    currentEvent = Event.VALUE_TRUE;
                    return true;
                case 'f':
                    readFalse();
                    currentEvent = Event.VALUE_FALSE;
                    return true;
                case 'n':
                    readNull();
                    currentEvent = Event.VALUE_NULL;
                    return true;
                case '0':
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':
                case '.':
                case '-':
                    readNumber(c, preEvent.peek() == Event.START_OBJECT, preEvent.peek() == Event.START_ARRAY);
                    currentEvent = Event.VALUE_NUMBER;
                    return true;
                case '"':
                default:
                    if (isReadValue) {
                        readString(c, preEvent.peek() == Event.START_OBJECT, preEvent.peek() == Event.START_ARRAY);
                        currentEvent = Event.VALUE_STRING;
                        return true;
                    } else {
                        readString(c, false, false);
                        currentEvent = Event.KEY_NAME;
                        return true;
                    }
            }
        }
        return false;
    }

    @Override
    public Event next() {
        return currentEvent;
    }

    @Override
    public String getString() {
        try {
            return StringEscapeUtils.unescapeJson(value.toString());
        } finally {
            value.setLength(0);
        }
    }

    @Override
    public boolean isIntegralNumber() {
        String s = value.toString();

        int result = 0;
        int i = 0, len = s.length();
        long limit = -Long.MAX_VALUE;
        long multmin;
        int digit;

        if (len > 0) {
            char firstChar = s.charAt(0);
            if (firstChar < '0') { // Possible leading "+" or "-"
                if (firstChar == '-') {
                    limit = Integer.MIN_VALUE;
                } else if (firstChar != '+') {
                    return false;
                }
                if (len == 1) { // Cannot have lone "+" or "-"
                    return false;
                }
                i++;
            }
            multmin = limit / 10;
            while (i < len) {
                // Accumulating negatively avoids surprises near MAX_VALUE
                digit = Character.digit(s.charAt(i++), 10);
                if (digit < 0) {
                    return false;
                }
                if (result < multmin) {
                    return false;
                }
                result *= 10;
                if (result < limit + digit) {
                    return false;
                }
                result -= digit;
            }
        } else {
            return false;
        }
        return true;
    }

    @Override
    public int getInt() {
        try {
            return new BigDecimal(value.toString()).intValue();
        } finally {
            value.setLength(0);
        }
    }

    @Override
    public long getLong() {
        try {
            return new BigDecimal(value.toString()).longValue();
        } finally {
            value.setLength(0);
        }
    }

    @Override
    public BigDecimal getBigDecimal() {
        try {
            return new BigDecimal(value.toString());
        } finally {
            value.setLength(0);
        }
    }

    @Override
    public JsonLocation getLocation() {
        throw new UnsupportedOperationException();
    }

    @Override
    public void close() {
        try {
            reader.close();
        } catch (IOException e) {
            throw new JsonException("关闭流失败", e);
        }
    }
}
